#include "gd32f3x0.h"
#include "log.h"
#include "Systick_Ctrl.h"
#include "cdc_acm_core.h"
#include "VCOM_Upgrade.h"

static int VCOM_Upgrade_RcvDataPacket(uint8_t* pData, int WantLen)
{
#define MAX_WAIT_TIMES 200
    int WaitTimes = 0;
    int CurRcvlen = 0;
    int TotalRcvlen = 0;
    uint8_t *pCurBuf = pData;
    
    while(TotalRcvlen < WantLen && WaitTimes < MAX_WAIT_TIMES)
    {
        CurRcvlen = cdc_acm_data_receive(&cdc_acm, pCurBuf, WantLen - TotalRcvlen);
        if(CurRcvlen > 0)
        {
            LOG_PRINT(("VCOM Rcv: %d\r\n", CurRcvlen));
            pCurBuf += CurRcvlen;
            TotalRcvlen += CurRcvlen;
        }
        CurRcvlen = 0;
        WaitTimes++;
        Systick_Delay(10);
    }
    
    if (TotalRcvlen == WantLen) 
    {
        return 0;
    }
    else
    {
        return -1;
    }
}

void VCOM_Upgrade_ClearRX(void)
{
    int len;
    
    do {
        len = cdc_acm_data_receive(&cdc_acm, gComMsgBuf, COM_PACKET_SIZE);
    } while(len != 0);
}

void VCOM_Upgrade_ContinueCheck(void)
{
    int len;
    uint8_t Response[3] = {'Y', 'E', 'S'};

    UPGRADE_LOG(("Continue Checking ...\r\n"));
    do {
        gUpgradeCmd[0] = 0;
        Systick_Delay(200);
        len = cdc_acm_data_receive(&cdc_acm, gUpgradeCmd, UPGRADE_CMD_BUF_SIZE);
        if (len > 0) {
            cdc_acm_data_send_data(&cdc_acm, Response, 3);
        }
        Systick_Delay(1000);
    } while(len > 0);
}

int VCOM_Upgrade_Check(void)
{
#define UPGRADE_CHECK_TIMES 5
    int CheckTimes = 0;
    uint8_t Response[3] = {'Y', 'E', 'S'};
    int len;

    UPGRADE_LOG(("Wait for VCOM upgrading...\r\n"));
    while (CheckTimes < UPGRADE_CHECK_TIMES) {
        gUpgradeCmd[0] = 0;
        len = cdc_acm_data_receive(&cdc_acm, gUpgradeCmd, UPGRADE_CMD_BUF_SIZE);
        if (len == UPGRADE_CMD_BUF_SIZE) {
            break;
        }
        Systick_Delay(100);
        CheckTimes++;
    }

    if(CheckTimes >= UPGRADE_CHECK_TIMES) {
        return -1;
    }

    for (CheckTimes = 0; CheckTimes < UPGRADE_CMD_BUF_SIZE; CheckTimes++) {
        if(gUpgradeCmd[CheckTimes] == 'C') {
            break;
        }
    }

    if (CheckTimes > (UPGRADE_CMD_BUF_SIZE - 6)) {
        return -1;
    }
    
    if (gUpgradeCmd[CheckTimes]     == 'C' && gUpgradeCmd[CheckTimes + 1] == 'O' && gUpgradeCmd[CheckTimes + 2] == 'M' &&
        gUpgradeCmd[CheckTimes + 3] == 'C' && gUpgradeCmd[CheckTimes + 4] == 'O' && gUpgradeCmd[CheckTimes + 5] == 'M') {
        UPGRADE_LOG(("VCOM Shakehands successfully\r\n"));
        gFMCWriteLen = 0;
        /*Send the ACK Command*/
        cdc_acm_data_send_data(&cdc_acm, Response, 3);
        VCOM_Upgrade_ContinueCheck();
        VCOM_Upgrade_ClearRX();
        VCOM_Upgrade_Process();
    }

    return -1;
}

void VCOM_Upgrade_Process(void)
{
    uint8_t Erase[5] = {'E', 'r', 'a', 's', 'e'};
    uint8_t Start[5] = {'S', 't', 'a', 'r', 't'};
    UART_MSG msg;
    uint32_t crc32, FlashWriteAddr = 0, NextIdx = 1;
    uint8_t* pData;
    int i, retry = 0;
    int upgradeOK = 1;

    gpUpgradePacket = (UPGRADE_PACKET*)(gComMsgBuf);

    Systick_Delay(500);
    VCOM_Upgrade_ClearRX();

    UPGRADE_LOG(("Erase flash: Start: 0x%X Size: %d pages\r\n", USER_APP_ADDR, USER_APP_PAGE_COUNT + USER_DATA_PAGE_COUT));
    cdc_acm_data_send_data(&cdc_acm, Erase, 5);

#ifdef USE_FMC_FLASH
    fmc_unlock();
    fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGERR);
    for(i = 0; i < USER_APP_PAGE_COUNT + USER_DATA_PAGE_COUT; i++)
    {
        fmc_page_erase(USER_APP_ADDR + i * FMC_PAGE_SIZE);
        fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGERR);
    }
#endif
    Systick_Delay(500);

    msg.MSG[0] = 'N'; msg.MSG[1] = 'E'; msg.MSG[2] = 'X'; msg.MSG[3] = 'T';
    cdc_acm_data_send_data(&cdc_acm, Start, 5);

    while(1)
    {
        VCOM_Upgrade_RcvDataPacket((uint8_t*)gpUpgradePacket, sizeof(UPGRADE_PACKET));
        crc32 = TiDTV_ComputeCRC32Value((uint8_t*)gpUpgradePacket, sizeof(UPGRADE_PACKET) - sizeof(uint32_t));
        UPGRADE_LOG(("Upgrade Packet: %d %d %d %d\r\n", gpUpgradePacket->curBlockIdx, gpUpgradePacket->totalBlocks,
                                                        gpUpgradePacket->BlockSize, gpUpgradePacket->crc32));
        UPGRADE_LOG(("CRC32: %d\r\n", crc32));
        if(crc32 == gpUpgradePacket->crc32 && gpUpgradePacket->curBlockIdx == NextIdx)
        {
            UPGRADE_LOG(("Got No. %d good packet\r\n", NextIdx));
            pData = gpUpgradePacket->data;
#ifdef USE_FMC_FLASH
            i = 0;
            FlashWriteAddr = USER_APP_ADDR + gFMCWriteLen;
            UPGRADE_LOG(("Will program Addr: 0x%X\r\n", FlashWriteAddr));
            while(i < UPGRADE_BUF_SIZE)
            {
                /* Write word into flash memory */
                if(fmc_word_program(FlashWriteAddr, *(__IO uint32_t *)(pData)) != FMC_READY)
                {
                    break;
                }
                pData += 4;
                FlashWriteAddr += 4;
                i += 4;
            }
#endif
            if(i == UPGRADE_BUF_SIZE)
            {
                UPGRADE_LOG(("Program No. %d packet OK\r\n", NextIdx));
                /*Next packet*/
                retry = 0;
                NextIdx++;
                gFMCWriteLen += i;
            }
        }
        msg.msgVal = NextIdx;
        retry++;
        
        if (retry >= MAX_RETRY_TIMES) {
            UPGRADE_LOG(("VCOM Upgrade ERROR!\r\n"));
#ifdef USE_FMC_FLASH
            fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGERR);
            fmc_page_erase(USER_APP_ADDR);
            fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_WPERR | FMC_FLAG_PGERR);
#endif
            msg.MSG[0] = 'E'; msg.MSG[1] = 'R'; msg.MSG[2] = 'R'; msg.MSG[3] = 'O';
            msg.msgVal = 0;
            cdc_acm_data_send_data(&cdc_acm, (uint8_t *)&msg, sizeof(msg));
            upgradeOK = 0;
            break;
        } 
        
        VCOM_Upgrade_ClearRX();
        VCOM_Upgrade_RcvDataPacket((uint8_t*)gpUpgradePacket, sizeof(UPGRADE_PACKET));
        if(gpUpgradePacket->curBlockIdx != gpUpgradePacket->totalBlocks && 
           gFMCWriteLen < USER_APP_SIZE)
        {
            cdc_acm_data_send_data(&cdc_acm, (uint8_t *)&msg, sizeof(msg));
        }
        else
        {
            UPGRADE_LOG(("VCOM Upgrade DONE!\r\n"));
            msg.MSG[0] = 'D'; msg.MSG[1] = 'O'; msg.MSG[2] = 'N'; msg.MSG[3] = 'E';
            msg.msgVal = 0;
            cdc_acm_data_send_data(&cdc_acm, (uint8_t *)&msg, sizeof(msg));
            break;
        }
    }

#ifdef USE_FMC_FLASH
    fmc_lock();
#endif
    gpUpgradePacket = 0;

    if (upgradeOK) {
        UPGRADE_LOG(("VCOM Upgrade OK! Will auto reboot!\r\n\r\n"));
        Systick_Delay(1000);
        iap_load_app(USER_APP_ADDR);
    }

    while (upgradeOK) {
        /*Waiting for Reboot*/
        Systick_Delay(100);
    }
}
